﻿using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks; 
namespace EFCoreLib01
{
    public class EFRepository<TDbContext, TEntity> : IRepository<TDbContext, TEntity> where TEntity : class where TDbContext : DbContext
    {
        private readonly IUnitOfWorkManager _unitOfWorkManager;

        public EFRepository(IUnitOfWorkManager unitOfWorkManager)
        {
            _unitOfWorkManager = unitOfWorkManager;
        }

        public virtual TDbContext CurrentDbContext => AsyncLocalCurrentUnitOfWorkHandleProvider<TDbContext>.Current?.GetActiveUnitOfWork();

        public IUnitOfWorkManager UnitOfWorkManager => _unitOfWorkManager;

        public IUnitOfWorkHandle<TDbContext> BeginUnitOfWork()
        {
            return _unitOfWorkManager.Begin<TDbContext>();
        }

        public IUnitOfWorkHandle<TDbContext> BeginNewUnitOfWork()
        {
            return _unitOfWorkManager.BeginNew<TDbContext>();
        }

        public TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate)
        {
            using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
            return uowHander.GetActiveUnitOfWork().Set<TEntity>().FirstOrDefault(predicate);
        }

        public async Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate)
        {
            using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
            return await uowHander.GetActiveUnitOfWork().Set<TEntity>().FirstOrDefaultAsync(predicate);
        }

        public IQueryable<TEntity> GetAll()
        {
            var context = CurrentDbContext;
            if (context != null)
            {
                return context.Set<TEntity>().AsQueryable();
            }
            throw new ArgumentNullException(nameof(CurrentDbContext));
        }

        public IQueryable<TEntity> GetAllIncluding(params Expression<Func<TEntity, object>>[] propertySelectors)
        {
            var context = CurrentDbContext;
            if (context != null)
            {
                var query = context.Set<TEntity>().AsQueryable();
                if (propertySelectors != null)
                {
                    foreach (var propertySelector in propertySelectors)
                    {
                        query = query.Include(propertySelector);
                    }
                }
                return query;
            }
            throw new ArgumentNullException(nameof(CurrentDbContext));
        }

        public List<TEntity> GetAllList()
        {
            using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
            return uowHander.GetActiveUnitOfWork().Set<TEntity>().ToList();

        }

        public List<TEntity> GetAllList(Expression<Func<TEntity, bool>> predicate)
        {
            using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
            return uowHander.GetActiveUnitOfWork().Set<TEntity>().Where(predicate).ToList();
        }

        public async Task<List<TEntity>> GetAllListAsync()
        {
            using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
            return await uowHander.GetActiveUnitOfWork().Set<TEntity>().ToListAsync();
        }

        public async Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate)
        {
            using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
            return await uowHander.GetActiveUnitOfWork().Set<TEntity>().Where(predicate).ToListAsync();
        }

        public T Query<T>(Func<IQueryable<TEntity>, T> queryMethod)
        {
            using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
            return queryMethod(GetAll());
        }

        public TEntity Single(Expression<Func<TEntity, bool>> predicate)
        {
            using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
            return uowHander.GetActiveUnitOfWork().Set<TEntity>().SingleOrDefault(predicate);
        }

        public async Task<TEntity> SingleAsync(Expression<Func<TEntity, bool>> predicate)
        {
            using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
            return await uowHander.GetActiveUnitOfWork().Set<TEntity>().SingleOrDefaultAsync(predicate);
        }

        public TEntity Insert(TEntity entity)
        {
            using var uowHander = _unitOfWorkManager.Begin<TDbContext>();

            entity = uowHander.GetActiveUnitOfWork().Set<TEntity>().Add(entity).Entity;

            uowHander.SaveChange();
            return entity;
        }

        public async Task<TEntity> InsertAsync(TEntity entity)
        {
            using var uowHander = _unitOfWorkManager.Begin<TDbContext>();

            entity = uowHander.GetActiveUnitOfWork().Set<TEntity>().Add(entity).Entity;

            await uowHander.SaveChangeAsync();
            return entity;
        }


        public TEntity Update(TEntity entity)
        {
            using var uowHander = _unitOfWorkManager.Begin<TDbContext>();

            var context = uowHander.GetActiveUnitOfWork();
            AttachIfNot(context, entity);
            context.Entry(entity).State = EntityState.Modified;

            uowHander.SaveChange();
            return entity;
        }

        public async Task<TEntity> UpdateAsync(TEntity entity)
        {
            using var uowHander = _unitOfWorkManager.Begin<TDbContext>();

            var context = uowHander.GetActiveUnitOfWork();
            AttachIfNot(context, entity);
            context.Entry(entity).State = EntityState.Modified;

            await uowHander.SaveChangeAsync();
            return entity;

        }

        public void Delete(TEntity entity)
        {
            using var uowHander = _unitOfWorkManager.Begin<TDbContext>();

            var context = uowHander.GetActiveUnitOfWork();
            AttachIfNot(context, entity);
            context.Set<TEntity>().Remove(entity);

            uowHander.SaveChange();
        }

        public async Task DeleteAsync(TEntity entity)
        {
            using var uowHander = _unitOfWorkManager.Begin<TDbContext>();

            var context = uowHander.GetActiveUnitOfWork();
            AttachIfNot(context, entity);
            context.Set<TEntity>().Remove(entity);

            await uowHander.SaveChangeAsync();
        }

        protected virtual void AttachIfNot(DbContext dbContext, TEntity entity)
        {
            var entry = dbContext.ChangeTracker.Entries().FirstOrDefault(ent => ent.Entity == entity);
            if (entry != null)
            {
                return;
            }
            dbContext.Set<TEntity>().Attach(entity);
        }
    }
}